package com.smart.milvus.model.metadata;

import com.alibaba.fastjson.JSONObject;
import com.smart.milvus.annotation.MilvusColumn;
import com.smart.milvus.autoconfigure.MilvusProperties;
import com.smart.milvus.base.DynamicCollectionHandler;
import com.smart.milvus.base.FieldWrapperHandler;
import com.smart.milvus.base.TypeHandler;
import com.smart.milvus.bean.GlobalConfig;
import com.smart.milvus.bean.MilvusModel;
import com.smart.milvus.bean.UnknownTypeHandler;
import com.smart.milvus.exception.MilvusException;
import com.smart.milvus.util.BeanUtil;
import com.smart.milvus.util.StringUtils;
import io.milvus.grpc.DataType;
import io.milvus.param.IndexType;
import io.milvus.param.MetricType;
import lombok.Data;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;

@Data
public class ColumnDefinition {

    /**
     * 默认表主键名称
     */
    private static final String DEFAULT_ID_NAME = "id";
    /**
     * 实体类型
     */
    private Class<?> entityType;
    /**
     * 字段
     */
    private Field field;
    /**
     * 属性名
     */
    private final String property;
    /**
     * 是否是主键
     */
    private boolean primary;
    /**
     * 字段名
     */
    private String name;
    /**
     * 数据类型
     */
    private DataType dataType;
    /**
     * 字段描述
     */
    private String desc;
    /**
     * 数组类型字段的元素类型
     */
    private DataType elementType;
    /**
     * 数组类型字段的最大容量
     */
    private int maxCapacity;
    /**
     * varchar类型最大长度, 其他类型不用指定
     */
    private Integer maxLength = 512;
    /**
     * 是否将该字段置为分区键
     */
    private boolean partitionKey = false;
    /**
     * 是否基于该字段创建索引
     */
    private boolean index = false;
    /**
     * 索引类型
     * <a href="https://milvus.io/docs/index.md">...</a>
     */
    private IndexType indexType = IndexType.HNSW;
    /**
     * 度量类型
     * <a href="https://milvus.io/docs/metric.md">...</a>
     */
    private MetricType metricType = MetricType.L2;
    /**
     * 向量维度,其他类型不用指定
     */
    private Integer vectorDimension = 1024;
    /**
     * 数据向量化处理的handler
     */
    private FieldWrapperHandler<?> fieldWrapperHandler;
    /**
     * 类型处理程序
     */
    private TypeHandler typeHandler;
    /**
     * 集合处理程序
     */
    private DynamicCollectionHandler collectionHandler;

    public ColumnDefinition() {
        this.property = "";
    }


    /**
     * 全新的 存在 CollectionField 注解时使用的构造函数
     *
     * @param collectionDefinition collection信息
     * @param field                字段
     * @param collectionColumn     字段注解
     */
    public ColumnDefinition(CollectionDefinition collectionDefinition, Field field, MilvusColumn collectionColumn) {
        GlobalConfig globalConfig = GlobalConfig.INSTANCE;
        MilvusProperties milvusProperties = globalConfig.getMilvusProperties();
        MilvusProperties.MilvusConfig dbConfig = milvusProperties.getGlobalConfig();

        this.entityType = collectionDefinition.getEntityType();
        this.field = field;
        this.property = field.getName();
        this.dataType = collectionColumn.dataType();
        this.desc = collectionColumn.desc();
        this.primary = collectionColumn.primary();
        this.fieldWrapperHandler = GlobalConfig.CTX.getBean(collectionColumn.fieldWrapper());;
        this.partitionKey = collectionColumn.partitionKey();
        this.vectorDimension = collectionColumn.vectorDimension();
        this.maxLength = collectionColumn.maxLength();
        this.index = collectionColumn.index();
        this.indexType = collectionColumn.indexType();
        this.metricType = collectionColumn.metricType();
        this.elementType = collectionColumn.elementType();
        this.maxCapacity = collectionColumn.maxCapacity();
        this.collectionHandler = collectionDefinition.getCollectionHandler();

        if (this.dataType == DataType.UNRECOGNIZED) {
            this.dataType = resolve(field.getType(), field.getGenericType());
        }

        if (collectionColumn.typeHandler() != TypeHandler.class) {
            this.typeHandler = createInstance(collectionColumn.typeHandler());
        }

        String column = collectionColumn.name();
        if (StringUtils.isBlank(column)) {
            column = this.property;
            if (collectionDefinition.isUnderCamel()) {
                /* 开启字段下划线申明 */
                column = StringUtils.camelToUnderline(column);
            }
            if (dbConfig.isCapitalMode()) {
                /* 开启字段全大写申明 */
                column = column.toUpperCase();
            }
        }
        String columnFormat = dbConfig.getColumnFormat();
        if (StringUtils.isNotBlank(columnFormat)) {
            column = String.format(columnFormat, column);
        }

        this.name = column;
    }

    /**
     * 不存在 CollectionField 注解时, 使用的构造函数
     *
     * @param collectionDefinition collection信息
     * @param field                字段
     */
    public ColumnDefinition(CollectionDefinition collectionDefinition, Field field) {
        GlobalConfig globalConfig = GlobalConfig.INSTANCE;
        MilvusProperties milvusProperties = globalConfig.getMilvusProperties();
        MilvusProperties.MilvusConfig dbConfig = milvusProperties.getGlobalConfig();

        this.entityType = collectionDefinition.getEntityType();
        this.field = field;
        this.property = field.getName();
        this.desc = "";
        this.primary = DEFAULT_ID_NAME.equals(this.property);
        this.dataType = resolve(field.getType(), field.getGenericType());
        this.fieldWrapperHandler = GlobalConfig.CTX.getBean(UnknownTypeHandler.class);
        this.collectionHandler = collectionDefinition.getCollectionHandler();

        String column = this.property;
        if (collectionDefinition.isUnderCamel()) {
            /* 开启字段下划线申明 */
            column = StringUtils.camelToUnderline(column);
        }
        if (dbConfig.isCapitalMode()) {
            /* 开启字段全大写申明 */
            column = column.toUpperCase();
        }

        String columnFormat = dbConfig.getColumnFormat();
        if (StringUtils.isNotBlank(columnFormat)) {
            column = String.format(columnFormat, column);
        }

        this.name = column;
    }

    private DataType resolve(Class<?> fieldType, Type type) {
        if (String.class.isAssignableFrom(fieldType)) {
            return DataType.VarChar;
        } else if (Integer.class.isAssignableFrom(fieldType)) {
            return DataType.Int32;
        } else if (Boolean.class.isAssignableFrom(fieldType)) {
            return DataType.Bool;
        } else if (Byte.class.isAssignableFrom(fieldType)) {
            return DataType.Int8;
        } else if (Character.class.isAssignableFrom(fieldType)) {
            return DataType.Int16;
        } else if (Float.class.isAssignableFrom(fieldType)) {
            return DataType.Float;
        } else if (Double.class.isAssignableFrom(fieldType)) {
            return DataType.Double;
        } else if (List.class.isAssignableFrom(fieldType) &&
                type instanceof ParameterizedType &&
                Float.class.isAssignableFrom((Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0])) {
            return DataType.FloatVector;
        } else if (fieldType.isArray() || Collection.class.isAssignableFrom(fieldType)) {
            return DataType.Array;
        } else if (JSONObject.class.isAssignableFrom(fieldType) || BeanUtil.isBean(fieldType)) {
            return DataType.JSON;
        } else {
            throw new IllegalArgumentException("Unsupported field type: " + fieldType);
        }
    }

    public Integer getVectorDimension() {
        if (collectionHandler != null) {
            return collectionHandler.vectorDimension((Class<? extends MilvusModel<?>>) entityType, field);
        }
        return vectorDimension;
    }

    public boolean isJsonField() {
        return this.dataType == DataType.JSON;
    }

    public boolean isJsonArray() {
        return this.elementType == DataType.JSON;
    }

    @SuppressWarnings("unchecked")
    public <T> FieldWrapperHandler<T> getFieldWrapperHandler() {
        return (FieldWrapperHandler<T>) fieldWrapperHandler;
    }

    public boolean canBePartitionKey() {
        return dataType == DataType.Int64 || dataType == DataType.VarChar;
    }

    public boolean vectorColumn() {
        return DataType.BinaryVector == dataType || DataType.FloatVector == dataType;
    }

    private <T> T createInstance(Class<T> clazz) throws MilvusException {
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            throw new MilvusException(e.getMessage());
        }
    }
}
